"""
@-*- coding: utf-8 -*-
@ python：python 3.8
@ 创建人员：ylx
@ 创建时间：2025/4/25 下午3:45
@ 内容：编写简单的装饰器及生成装饰器的函数
"""
"""
为什么要使用装饰器：
1.使用装饰器可以提升代码复用，避免重复冗余代码
如果你有多个函数需要测量运行时间，可以直接将装饰器应用在这些函数上
而不是给函数增加上一样的代码，这样代码冗余，后续不好维护

2.使用装饰器可以保证函数的逻辑清晰，如果一个本身就很复杂的函数，我还要通过修改函数内部的代码
来测量运行时间，这样会模糊函数自身的主要逻辑，同时软件开发的原则就是单一职责
既一个函数只应该承担一项责任

3.通过装饰器，我们可以扩展别人写好的函数，可以添加额外行为，而不需要修改别人的源代码
比如说你正在使用一个第三方库的函数，需要添加额外行为，比如说给函数运行时间进行测量，那你就可以使用
装饰器

理解装饰器的运行流程，尤其是含参装饰器的运行流程，对下面-嵌套函数的运行流程需要掌握

嵌套函数形如
def out_function():
    print(1)
    
    def inner_function():
        print(2)

    print(3)
    inner_function()
    

外部函数 out_function 是顶层函数，可以直接被调用
它包含了一个嵌套的内部函数 inner_function

内部函数 inner_function  定义在 外部函数的  内部作用域 内
它只能在外部函数的  内部 被调用，也就是说只有别人调用外部函数后，若外部函数内写了
调用内部函数的逻辑，那你就可以触发内部函数内的逻辑
，而你不能直接从外部直接调用inner_function()

如果我现在调用
out_function() 它的执行流程如下
先执行 print(1)
然后 定义内部函数 inner_function  此时不会执行，因为此时仅仅是定义了函数

然后 print(3)
然后调用 inner_function() 此时这个内部函数才会被执行  执行其  print(2)

此处一定要注意 这个内部函数 如果没有被调用 实际上是不执行的，仅仅完成定义
在外部函数的内部调用 内部函数后，Python会跳转到内部函数的定义并执行代码，
内部执行完成后，控制权返回到外部函数，若外部函数没有其他代码，则外部函数执行结束

作用域规则
内部函数的作用域，内部函数可以访问外部函数的变量  称为闭包
外部函数无法访问内部函数的变量，内部函数的变量仅在自身作用域内有效

闭包：若内部函数引用了外部函数的变量，并且外部函数返回了内部函数，
那么内部函数就会携带这些变量的引用 形成一个闭包
def outer_function():
    x = "local to outer"

    def inner_function():
        print(f"Inner: {x}")

    return inner_function

closure = outer_function()  # 返回内部函数
closure()  # 调用内部函数--既执行内部函数

上述-即使 outer_function 已经执行完毕，inner_function 仍然可以访问外部函数的变量 x
看起来outer_function 仅仅是执行了一个返回 inner_function的操作
但是这样的操作，就可以使得 inner_function() 仍然可以访问 外部函数的变量x

嵌套函数的用途：
1.封装逻辑：
嵌套函数可以将辅助逻辑封装在外部函数中，避免污染全局命名空间。
def calculate_area(radius):
    def validate_radius(r):
        if r < 0:
            raise ValueError("Radius cannot be negative")
        return r

    radius = validate_radius(radius)
    return 3.14 * radius ** 2

print(calculate_area(5))  # 输出：78.5

2.工厂函数-嵌套函数可以用来动态的生成函数
def power_generator(n):
    def nth_power(x):
        return x ** n

    return nth_power

square = power_generator(2)  # 生成平方函数
cube = power_generator(3)    # 生成立方函数

print(square(4))  # 输出：16
print(cube(3))    # 输出：27


    

嵌套函数的执行流程：
外部函数首先执行自己的显示代码。
内部函数只有在被显式调用时才会执行。
内部函数可以访问外部函数的变量，但外部函数无法访问内部函数的变量。


普通装饰器
def my_decorator(func):  # func 是被装饰的函数
    def wrapper(*args, **kwargs):
        print("Before function call")
        result = func(*args, **kwargs)
        print("After function call")
        return result
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
执行流程：
@my_decorator 等价于 say_hello = my_decorator(say_hello)。

自己理解-那这样的话由于我们上面学习了闭包的知识，此时我调用my_decorator()时将我之前需要被装饰的
func传递进入，这个方法会返回一个wrapper函数，请注意  相当于我的say_hello = wrapper函数了
此时我继续执行say_hello()时-等于触发了wrapper()，这也是闭包的原理，导致我可以接收外层函数的返回，并由此来触发外层函数
内部的 被定义的函数，故由此完成了简单的装饰器的 运行流程

my_decorator 接收 func（即 say_hello），并返回 wrapper。
调用 say_hello() 实际上调用的是 wrapper。
在这种情况下，func 是直接传递给装饰器函数的。



生成装饰器的函数，既你的装饰器需要支持参数，你想根据不同的参数动态的生成不同的装饰器行为，这时需要引入一层额外的函数
def decorator_factory(param):
    def actual_decorator(func):  # func 是被装饰的函数
        def wrapper(*args, **kwargs):
            print(f"Decorator parameter: {param}")
            print("Before function call")
            result = func(*args, **kwargs)
            print("After function call")
            return result
        return wrapper
    return actual_decorator

@decorator_factory(param="example")
def say_hello():
    print("Hello!")

say_hello()

执行流程：
语法糖展开-say_hello = decorator_factory(param="example")(say_hello)
这里有两个步骤：
调用最外层函数 decorator_factory(param="example")。
将返回值（即 actual_decorator）作用于 say_hello
先调用最外层函数时，返回了真正的装饰器函数
此时相当于 返回的真正的装饰器函数 作用于 我的func,指需要被装饰的函数--可以写为 actual_decorator(func)现在开始被调用
那执行这个就会返回 wrapper函数--至此 原来的 say_hello = wrapper() 所以，你执行say_hello(),本质上相当于你触发执行了
wrapper()函数，由于Python的闭包机制，导致你的wrapper可以访问外层函数的变量，从而完成，按照参数生成装饰器的需求


至此相关理解就成了简单装饰器，至于为什么参数可以被使用，这还是闭包的原理，导致内部函数可以访问
外部函数作用域内的变量
上述的例子是直接在外部函数 内 写了一个 a = "xxx",那如果能这样
就相当于我可以给外部函数传递一个变量，那内部的函数也是可以使用的




调用 decorator_factory(param="example")：
decorator_factory 接收到参数 param="example"。
返回内部的 actual_decorator 函数。
调用 actual_decorator(say_hello)：
actual_decorator 接收到被装饰的函数 func=say_hello。
返回 wrapper 函数。
替换原始函数：
say_hello 被替换为 wrapper 函数。
此时，调用 say_hello() 实际上是在调用 wrapper。


为什么上述这种使用语法糖时，func没有直接先传递给外部函数呢？
语法糖的要求
Python 的装饰器语法糖（@decorator）要求装饰器本身是一个可以接受函数作为参数的函数。具体来说：
如果装饰器不带参数（普通装饰器），语法糖会将被装饰的函数直接传递给装饰器。
如果装饰器带参数（生成装饰器的函数），语法糖会先调用最外层函数（decorator_factory），然后将被装饰的函数传递给中间层函数（actual_decorator）。



如果的被装饰函数是含参数的，那么我们怎么处理？
其实和简单的装饰器一样，如果你仅仅是被装饰的函数含有参数，那这个参数会被传递给wrapper，
因为wrapper才是你真正执行的函数
wrapper最后又会吐给wrapper里的 func，相当于原本属于func的参数最后又回到了func
这就理解为 你调用say_hello("liming")  实际上最后调用的是 wrapper("liming")，这个参数最后会被
wrapper里的func所使用，而def wrapper时，仅仅是作为承接不定长位置  不定长关键字参数而做的，也就是为了承接传递
func本来需要的参数而做的，因为func(*args, **kwargs) 是会将元组或字典解包成，需要的
位置参数 和 关键字参数

因为你的say_hello()  最后实际上是 say_hello = wrapper,所以最后触发的是wrapper(*args， **kwargs)


"""


# import time

# # 简单的装饰器
# def decorator(func):
#     def wrapper(*args, **kwargs):
#         print("开始")
#         start_time = time.time()
#         result = func()
#         end_time = time.time()
#         print("结束")
#         print(f"花费时间{end_time - start_time}")
#         return result
#
#     return wrapper
#
#
# @decorator
# def say():
#     time.sleep(1)
#     print(f"hello liming")
#
#
# if __name__ == "__main__":
#     say("liming")


# import time
#
#
# # 含参装饰器，既生成不同装饰器的函数
# def count(times):
#     def decorator(func):
#         def wrapper(*args, **kwargs):
#             start_time = time.time()
#             result = func(*args, **kwargs)
#             end_time = time.time()
#             if end_time - start_time > times:
#                 print(f"{func.__name__} took longer than {times}")
#             else:
#                 print(f"{func.__name__} took shorter than {times}")
#             return result
#
#         return wrapper
#
#     return decorator
#
#
# @count(1)
# def say_1(name):
#     time.sleep(2)
#     print(f"hello, {name}")
#
#
# @count(0.5)
# def say_2():
#     time.sleep(0.2)
#     print("hello")
#
#
# if __name__ == "__main__":
#     say_1("liming")
#     print(say_1.__name__)
#     say_2()
#     print(say_2.__name__)


def time(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if times > 2:
                print("introduce new student")
                result = func(*args, **kwargs)
                print("very,good")
            else:
                result = func(*args, **kwargs)

            return result

        return wrapper

    return decorator


@time(12)
def say_hello(name, school="small"):
    return print(f"hello my name is {name},my school is {school}")


say_hello("liming", school="big")
